/*******************************************************************************
 * Copyright (c) 2005, 2007 Intel Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * Intel Corporation - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.managedbuilder.internal.macros;

import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.cdtvariables.CdtVariableException;
import org.eclipse.cdt.core.cdtvariables.ICdtVariable;
import org.eclipse.cdt.core.cdtvariables.ICdtVariableManager;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.managedbuilder.core.IBuildObject;
import org.eclipse.cdt.managedbuilder.core.IBuilder;
import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.IInputType;
import org.eclipse.cdt.managedbuilder.core.IManagedBuildInfo;
import org.eclipse.cdt.managedbuilder.core.IManagedProject;
import org.eclipse.cdt.managedbuilder.core.IOutputType;
import org.eclipse.cdt.managedbuilder.core.IResourceInfo;
import org.eclipse.cdt.managedbuilder.core.ITool;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.cdt.managedbuilder.macros.IBuildMacroProvider;
import org.eclipse.cdt.managedbuilder.macros.IReservedMacroNameSupplier;
import org.eclipse.cdt.utils.cdtvariables.CdtVariableResolver;
import org.eclipse.cdt.utils.cdtvariables.IVariableContextInfo;
import org.eclipse.cdt.utils.cdtvariables.SupplierBasedCdtVariableSubstitutor;
import org.eclipse.cdt.utils.envvar.EnvVarOperationProcessor;
import org.eclipse.core.resources.IResource;

/**
 * This substitutor resolves all macro references except for the environment macro references
 * If a user has chosen to keep those macros in the buildfile, the environment macro references
 * are converted to the buildfile variable references, otherwise those macros are also resolved
 *
 * @see org.eclipse.cdt.managedbuilder.internal.macros#IMacroSubstitutor
 * @since 3.0
 */
public class BuildfileMacroSubstitutor extends SupplierBasedCdtVariableSubstitutor {
	private static final String PATTERN_MACRO_NAME = "="; //$NON-NLS-1$
	private IConfiguration fConfiguration;
	private IBuilder fBuilder;
	private HashSet<String> fCaseInsensitiveReferencedNames;
	private ICdtVariableManager fVarMngr;
	private ICConfigurationDescription fCfgDes;

	private class DefaultReservedMacroNameSupplier implements IReservedMacroNameSupplier{
		String fReservedNames[];

		public DefaultReservedMacroNameSupplier(IConfiguration configuration){
			IBuilder builder = configuration.getToolChain().getBuilder();
			String reservedNames[] = builder.getReservedMacroNames();
			String buildVars[] = getConfigurationReservedNames(configuration);

			if(reservedNames == null || reservedNames.length == 0)
				fReservedNames = buildVars;
			else if(buildVars == null || buildVars.length == 0)
				fReservedNames = reservedNames;
			else {
				fReservedNames = new String[reservedNames.length + buildVars.length];
				System.arraycopy(reservedNames,0,fReservedNames,0,reservedNames.length);
				System.arraycopy(buildVars,0,fReservedNames,reservedNames.length,buildVars.length);
			}
		}

		/* (non-Javadoc)
		 * @see org.eclipse.cdt.managedbuilder.macros.IReservedMacroNameSupplier#isReservedName(java.lang.String, org.eclipse.cdt.managedbuilder.core.IConfiguration)
		 */
		@Override
		public boolean isReservedName(String macroName, IConfiguration configuration) {
			if(fReservedNames != null && fReservedNames.length > 0){
				for(int i = 0; i < fReservedNames.length; i++){
					Pattern p = Pattern.compile(fReservedNames[i]);
					Matcher m = p.matcher(macroName);
					if(m.matches())
						return true;
				}
			}
			return false;
		}

		protected String[] getConfigurationReservedNames(IConfiguration configuration){
			ITool tools[] = configuration.getFilteredTools();
			if(tools != null){
				Set<String> set = new HashSet<String>();
				for(int i = 0; i < tools.length; i++){
					IOutputType ots[] = tools[i].getOutputTypes();
					if(ots != null){
						for(int j = 0; j < ots.length; j++){
							String varName = ots[j].getBuildVariable();
							if(varName != null){
								set.add(varName);
							}
						}
					}

					IInputType its[] = tools[i].getInputTypes();
					if(its != null){
						for(int j = 0; j < its.length; j++){
							String varName = its[j].getBuildVariable();
							if(varName != null){
								set.add(varName);
							}
						}
					}

				}

				return set.toArray(new String[set.size()]);
			}
			return null;
		}
	}

//	public BuildfileMacroSubstitutor(int contextType, Object contextData, String inexistentMacroValue, String listDelimiter){
//		super(contextType, contextData, inexistentMacroValue, listDelimiter);
//		init();
//	}

	public BuildfileMacroSubstitutor(IBuilder builder, IMacroContextInfo contextInfo, String inexistentMacroValue, String listDelimiter){
		super(contextInfo, inexistentMacroValue, listDelimiter);
		init(builder, contextInfo);
	}

	public BuildfileMacroSubstitutor(IMacroContextInfo contextInfo, String inexistentMacroValue, String listDelimiter){
		this(null, contextInfo, inexistentMacroValue, listDelimiter);
	}



	private void init(IBuilder builder, IMacroContextInfo contextInfo){
		if(contextInfo == null)
			return;

		fVarMngr = CCorePlugin.getDefault().getCdtVariableManager();

		if(builder != null){
			fBuilder = builder;
			fConfiguration = builder.getParent().getParent();
		} else {
			IBuildObject[] bos = findConfigurationAndBuilderFromContext(contextInfo);
			if(bos != null){
				fConfiguration = (IConfiguration)bos[0];
				fBuilder = (IBuilder)bos[1];
			}
		}

		if(fConfiguration != null){
			fCfgDes = ManagedBuildManager.getDescriptionForConfiguration(fConfiguration);
		}
	}

	static IBuildObject[] findConfigurationAndBuilderFromContext(IMacroContextInfo contextInfo){
		int type = contextInfo.getContextType();
		IConfiguration cfg = null;
		IBuilder builder = null;
		switch(type){
		case IBuildMacroProvider.CONTEXT_FILE:
			contextInfo = (IMacroContextInfo)contextInfo.getNext();
			if(contextInfo == null)
				break;
		case IBuildMacroProvider.CONTEXT_OPTION:
			contextInfo = (IMacroContextInfo)contextInfo.getNext();
			if(contextInfo == null)
				break;
		case IBuildMacroProvider.CONTEXT_CONFIGURATION:{
				Object contextData = contextInfo.getContextData();
				if(contextData instanceof IConfiguration){
					cfg = (IConfiguration)contextData;
					builder = cfg.getBuilder();
				} else if (contextData instanceof IBuilder){
					builder = (IBuilder)contextData;
					cfg = builder.getParent().getParent();
				} else if (contextData instanceof ITool) {
					ITool tool = (ITool)contextData;
					IResourceInfo rcInfo = tool.getParentResourceInfo();
					if(rcInfo != null){
						cfg = rcInfo.getParent();
						if(cfg != null){
							builder = cfg.getBuilder();
						}
					}
				}
			}
			break;
		case IBuildMacroProvider.CONTEXT_PROJECT:{
				Object contextData = contextInfo.getContextData();
				if(contextData instanceof IManagedProject){
					IResource rc = ((IManagedProject)contextData).getOwner();
					if(rc != null){
						IManagedBuildInfo info = ManagedBuildManager.getBuildInfo(rc);
						cfg = info.getDefaultConfiguration();
						builder = cfg.getBuilder();
					}
				}
			}
			break;
		}

		if(cfg != null && builder != null)
			return new IBuildObject[]{cfg, builder};
		return null;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.managedbuilder.internal.macros.DefaultMacroSubstitutor#resolveMacro(org.eclipse.cdt.managedbuilder.macros.IBuildMacro)
	 */
	@Override
	protected ResolvedMacro resolveMacro(ICdtVariable macro) throws CdtVariableException{
		ResolvedMacro resolved = null;

		if(fConfiguration != null && fBuilder != null &&
				fBuilder.keepEnvironmentVariablesInBuildfile() &&
				fVarMngr.isEnvironmentVariable(macro, fCfgDes) &&
				(!CdtVariableResolver.isStringListVariable(macro.getValueType())
						|| size(macro.getStringListValue()) < 2)){
			String ref = getMacroReference(macro);
			if(ref != null)
				resolved = new ResolvedMacro(macro.getName(),ref);

		}
		if(resolved != null)
			return resolved;
		return super.resolveMacro(macro);
	}

	private static int size(String[] value){
		return value != null ? value.length : 0;
	}

	public IConfiguration getConfiguration(){
		return fConfiguration;
	}

	protected IReservedMacroNameSupplier getReservedMacroNameSupplier(){
		if(fBuilder == null)
			return null;
		IReservedMacroNameSupplier supplier = fBuilder.getReservedMacroNameSupplier();
		if(supplier == null)
			supplier = new DefaultReservedMacroNameSupplier(fConfiguration);

		return supplier;
	}

	protected String getMacroReference(ICdtVariable macro){
		String macroName = macro.getName();
		String ref = null;
		IReservedMacroNameSupplier supplier = getReservedMacroNameSupplier();
		//on win32 all environment variable names are converted to upper case
		macroName = EnvVarOperationProcessor.normalizeName(macroName);
		if(supplier == null || !supplier.isReservedName(macroName,fConfiguration)){
			String pattern = fBuilder.getBuilderVariablePattern();
			if(pattern != null && pattern.indexOf(PATTERN_MACRO_NAME) != -1){
				if(fBuilder.isVariableCaseSensitive() || getCaseInsensitiveReferencedNames().add(macroName.toUpperCase())){
					ref = pattern.replaceAll(PATTERN_MACRO_NAME,macroName);
				}
			}
		}
		return ref;
	}

	protected Set<String> getCaseInsensitiveReferencedNames(){
		if(fCaseInsensitiveReferencedNames == null)
			fCaseInsensitiveReferencedNames = new HashSet<String>();
		return fCaseInsensitiveReferencedNames;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.managedbuilder.internal.macros.DefaultMacroSubstitutor#setMacroContextInfo(org.eclipse.cdt.managedbuilder.internal.macros.IMacroContextInfo)
	 */
	@Override
	public void setMacroContextInfo(IVariableContextInfo info)
				throws CdtVariableException{
		super.setMacroContextInfo(info);
		if(info instanceof IMacroContextInfo)
			init(null, (IMacroContextInfo)info);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.managedbuilder.internal.macros.IMacroSubstitutor#setMacroContextInfo(int, java.lang.Object)
	 */
//	public void setMacroContextInfo(int contextType, Object contextData) throws BuildMacroException{
//		super.setMacroContextInfo(contextType, contextData);
//		init();
//	}

}
